package com.xuecheng.ucenter.service.impl;

import com.alibaba.fastjson.JSON;
import com.xuecheng.ucenter.mapper.XcMenuMapper;
import com.xuecheng.ucenter.model.dto.AuthParamsDto;
import com.xuecheng.ucenter.model.dto.XcUserExt;
import com.xuecheng.ucenter.model.po.XcMenu;
import com.xuecheng.ucenter.service.AuthService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
//UserDetailsService是一个bean所以使用@Component注解标记一下
@Component
@Slf4j
//在进行认证的时候会请求UserDetailsService这个接口
public class UserServiceImpl implements UserDetailsService {

    @Autowired
            //这里注入的是spring容器 里面有很多的bean 所以可以在这里面根据下面拼出的bean的名字注入对应的bean也就是authService
            //因为authService有多个实现类 所以需要进行这样的操作
    ApplicationContext applicationContext;
    @Autowired
    private XcMenuMapper xcMenuMapper;

    @Override
    //loadUserByUsername这个就是流程图中DaoAuthenticationProvider请求UserDetailsService获取数据路里面用户数据的方法 返回给DaoAuthenticationProvider进行校验
    //String s参数就是username 根据username从数据库里面查询用户信息（可以是微信的 也可以是本地的） 现在不是用户名了 而是变成了对象AuthParamsDto
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        //就是前端可能传过来需要验证的东西（参数）
        AuthParamsDto authParamsDto = null;
        try {
            //s表示的不是用户名了 而是变成了对象AuthParamsDto 前端点击登录的时候就会请求UserDetailsService接口把需要认证的参数传过来
             authParamsDto = JSON.parseObject(s, AuthParamsDto.class);
        } catch (Exception e) {
            throw new RuntimeException("请求认证的参数不符合要求");
        }
        //根据前端传过来的认证方式决定采用哪种方式对用户的密码进行验证 以前是直接返回userDetails丢给spring security框架进行验证
        //但是由于每一个登录方式的校验规则可能不同 所以需要自定义校验密码的逻辑
        String authType = authParamsDto.getAuthType();

        //beanName就是根据前端传过来的认证类型采取不同的校验密码的逻辑 也就是策略模式
        String beanName = authType+"_authservice";
        AuthService authService = applicationContext.getBean(beanName, AuthService.class);
        //在校验的逻辑里面返回的用户的信息
        XcUserExt xcUserExt = authService.execute(authParamsDto);

        UserDetails userDetails = getUserPrincipal(xcUserExt);
        //如果查到正确的用户密码 最终封装成一个UserDetails对象给spring security框架返回 由框架进行密码的比对 但是由于每一个登录方式的校验规则可能不同 所以需要自定义匹配的逻辑
        //如PasswordAuthServiceImpl或则WxAuthServiceImpl
        //返回userDetails然后spring security框架就会把userDetails放到上下文里面 则微服务就可以从这里面获取用户的信息以及对应的权限进行密码校验了
        return userDetails;
    }


    public UserDetails getUserPrincipal(XcUserExt xcUser){
        String password = xcUser.getPassword();
        //先设置一个默认的 最后覆盖掉就可以 因为如果不传这个参数方法会报错
        String authorities[] = {"test"};
        //从数据库进行查询出用户的信息 根据该用户的id查出所拥有权限的菜单信息
        List<XcMenu> xcMenus = xcMenuMapper.selectPermissionByUserId(xcUser.getId());
        if(xcMenus.size()>0){
            ArrayList<String> list = new ArrayList<>();
            xcMenus.forEach(xcMenu -> {
                //首先在selectPermissionByUserId是一个多表查询 查出当前用户所具有的权限然后放到xcMenus里面的Code属性里
                list.add(xcMenu.getCode());
            });
            //用户的信息里面由权限的信息
            authorities = list.toArray(new String[0]);
        }
        //userJSON是放在jwt里面的第二部分内容里面的 是对外公开的 所以密码要设置成空 防止泄露
        xcUser.setPassword(null);
        //怎么拓展用户的信息呢？就是在这里拿到用户的信息把需要拓展的信息在这里转成json串
        String userJSON = JSON.toJSONString(xcUser);
        //从数据库查询到的用户信息进行放入userDetails然后返回给框架  authorities这个是权限从数据库多表查询出来的 应该是在这里定义的权限 然后微服务根据这个权限看能不能访问接口
        //要进行jwt携带内容的拓展的话可以直接通过withUsername把拓展的信息都放到userJSON里面
        UserDetails userDetails = User.withUsername(userJSON).password(password).authorities(authorities).build();
        return userDetails;
    }
}
